//
//  Statement.swift
//  SLDatabase
//
//  Created by 姚胜龙 on 2017/7/4.
//  Copyright © 2017年 姚胜龙. All rights reserved.
//

import UIKit

/**
 * 专门做绑定参数的业务类
 * SQL语句：参数绑定(表字段的绑定)
 * 技术设计：函数重载+链式编程
 */

//定义两个全局变量
/**
 * SQLITE_STATIC（0）：表示->表指针对应的内容是恒定不变的（可以理解为表的内容不可以更改）
 * SQLITE_TRANSIENT（-1）：表示->表指针对应的数据是可以读写的，并且可以随时改变的
 */
let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)


public final class Statement {

    //要保存数据库的连接
    fileprivate var connection:DBConnection
    //数据库的表指针
    fileprivate var handler:OpaquePointer? = nil

    //第一步：实现构造方法
    init(_ connection:DBConnection, _ SQL:String) throws {
        self.connection = connection

        //预处SQL语句
        //参数一：数据库连接指针
        //参数二：SQL语句
        //参数三：SQL语句的长度 （-1 标示自动检测SQL语句长度，也就是第一个结束符，只处理第一个结束符之前的SQL语句，之后的就不处理了）
        //参数四：数据库的表指针
        //参数五：SQL语句可能存在多个结束符，指定执行 剩下没有执行的SQL(参数五和参数三是有关系的) 如果不需要执行传nil
        try connection.checkError(sqlite3_prepare_v2(connection.handle,
                                                     SQL,
                                                     -1,
                                                     &handler,
                                                     nil))
    }
    //析构函数释放指针
    deinit {
        //释放数据库的表指针
        sqlite3_finalize(handler)
    }

    //第二步：绑定参数
    /*****************绑定参数-- start --*************************/
    //可变参数
    public func bind(_ values:Binding?...) -> Statement {
        return self.bind(values)
    }
    //数组参数
    public func bind(_ values:[Binding?]) -> Statement {

        //判断参数是否为空
        if values.isEmpty {
            return self
        }
        //如果有参数，需要重置数据库的参数
        reset()

        guard values.count == Int(sqlite3_bind_parameter_count(handler)) else {
            fatalError("参数列表和数据库表字段数量不匹配！")
        }

        //专门做绑定,将数据绑定到SQL
        for index in 1...values.count {
            perBind(values[index - 1], index: index)
        }

        return self
    }
    //字典参数
    public func bind(_ values:[String :Binding?]) -> Statement {

        reset()//重置

        //绑定字典参数
        for (key, value) in values {
            //判断这个字段是否存在
            let index = sqlite3_bind_parameter_index(handler, key)
            guard index > 0 else {
                fatalError("没有这个表字段")
            }
            perBind(value, index: Int(index))
        }

        return self
    }

    //一个一个的参数绑定
    fileprivate func perBind(_ value:Binding? , index:Int) {
        if value == nil {
            sqlite3_bind_null(handler, Int32(index))
        }
        else if let v = value as? Double {
            sqlite3_bind_double(handler, Int32(index), v)
        }
        else if let v = value as? Float {
            sqlite3_bind_double(handler, Int32(index), Double(v))
        }
        else if let v = value as? Int {
            sqlite3_bind_int(handler, Int32(index), Int32(v))
        }
        else if let v = value as? Int32 {
            sqlite3_bind_int(handler, Int32(index), v)
        }
        else if let v = value as? Int64 {
            sqlite3_bind_int64(handler, Int32(index), v)
        }
        else if let v = value as? Bool {
            //Bool类型就要动态绑定
            self.perBind(v.dataTypeValue, index: index)
        }
        else if let v = value as? String {
            //参数1：表指针
            //参数2：表字段的下标
            //参数3：表字段对应的值
            //参数4：参数3字符串的长度（-1：标示系统自动计算）
            //参数5：是否缓存数据（SQLITE_TRANSIENT：解释见顶部）
            sqlite3_bind_text(handler,
                              Int32(index),
                              v,
                              -1,
                              SQLITE_TRANSIENT)
        }
        else {
            fatalError("没有指定的这个类型\(value!)")
        }
    }

    //清空缓存
    public func reset(clearBinding shouldClear:Bool = true) {
        //清空缓存，使得SQL语句可以重复使用
        /* 
         原始SQL：例如 "insert into t_user(name) values (?)"
         执行插入语句后 name = "YSL"
         SQL语句就变成了 "insert into t_user(name) values ("YSL")
         所以要做一次SQL还原操作，才能继续使用
         */
        sqlite3_reset(handler)
        if shouldClear {
            //清空缓存,绑定新的数据
            sqlite3_clear_bindings(handler)
        }
    }
    /*****************绑定参数-- end --*************************/


    /*****************执行SQL-- start --*************************/
    //可变参数 (链式编程，执行完成后返回自己)
    public func executeSQL(_ bindings:Binding?...) throws -> Statement {
        guard bindings.isEmpty else {
            //不为空 绑定
            return try self.executeSQL(bindings)
        }
        //如果为空，重新设置SQL语句 但是不清空参数
        reset(clearBinding: false)

        //执行SQL
        try step()

        return self

    }
    //数组参数
    public func executeSQL(_ bindings:[Binding?]) throws -> Statement {
        return try self.bind(bindings).executeSQL()
    }
    //字典参数
    public func executeSQL(_ bindings:[String: Binding?]) throws -> Statement {
        return try self.bind(bindings).executeSQL()
    }

    /*****************执行SQL-- end --*************************/

    //执行SQL语句
    @discardableResult public func step() throws -> Bool {
        return try self.connection.dbSync {
            try self.connection.checkError(sqlite3_step(self.handler)) == SQLITE_ROW
        }
    }
}

//打印一下SQL语句
extension Statement: CustomStringConvertible {
    public var description: String {
        return String(cString: sqlite3_sql(handler))
    }
}


